package jp.co.cyberagent.android.gpuvideo;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.opengl.GLSurfaceView;
import android.os.Build;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Size;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ProgressBar;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.view.ViewCompat;

import java.util.concurrent.Semaphore;

import jp.co.cyberagent.android.gpuimage.GLTextureView;
import jp.co.cyberagent.android.gpuimage.GPUImageNativeLibrary;
import jp.co.cyberagent.android.gpuvideo.util.CameraLoader;

/**
 * @author wenjie
 */
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class GPUVideoCameraView extends GPUVideoView {
    public Size forceSize = null;
    private boolean isShowLoading = false;
    private CameraLoader cameraLoader;

    public GPUVideoCameraView(Context context) {
        super(context);
    }

    public GPUVideoCameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onInit(TypedArray a) {
        super.onInit(a);
        isShowLoading = a.getBoolean(R.styleable.GPUImageView_gpuimage_show_loading, isShowLoading);
    }

    @NonNull
    @Override
    protected GLSurfaceView onCreateGLSurfaceView(Context context, AttributeSet attrs) {
        return new GPUVideoGLSurfaceView(context, attrs);
    }

    @NonNull
    @Override
    protected GLTextureView onCreateGLTextureView(Context context, AttributeSet attrs) {
        return new GPUVideoGLTextureView(context, attrs);
    }

    @Override
    public void onResume() {
        super.onResume();
        if (ViewCompat.isLaidOut(this) && !isLayoutRequested()) {
            if (cameraLoader != null) {
                cameraLoader.onResume(getWidth(), getHeight());
            }
        } else {
            addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    removeOnLayoutChangeListener(this);
                    if (cameraLoader != null) {
                        cameraLoader.onResume(getWidth(), getHeight());
                    }
                }
            });
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (cameraLoader != null) {
            cameraLoader.onPause();
        }
    }

    @Override
    public void release() {
        super.release();
        cameraLoader = null;
    }

    public void setDataSource(CameraLoader cameraLoader) {
        this.cameraLoader = cameraLoader;
        gpuVideo.getRenderer().setOnSurfaceCreateListener(cameraLoader::setUpSurface);
        cameraLoader.setOnCameraSizeChangeListener(gpuVideo.getRenderer());
        setRotation(cameraLoader.getCameraOrientation());
        setRenderMode(GPUVideoCameraView.RENDERMODE_CONTINUOUSLY);
    }

    public boolean hasMultipleCamera() {
        if (cameraLoader != null) {
            return cameraLoader.hasMultipleCamera();
        }
        return false;
    }

    public void switchCamera() {
        if (hasMultipleCamera()) {
            cameraLoader.switchCamera();
            setRotation(cameraLoader.getCameraOrientation());
        }
    }

    /**
     * Retrieve current image with filter applied and given size as Bitmap.
     *
     * @param width  requested Bitmap width
     * @param height requested Bitmap height
     * @return Bitmap of picture with given size
     * @throws InterruptedException
     */
    public Bitmap capture(final int width, final int height) throws InterruptedException {
        // This method needs to run on a background thread because it will take a longer time
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new IllegalStateException("Do not call this method from the UI thread!");
        }

        forceSize = new Size(width, height);

        final Semaphore waiter = new Semaphore(0);

        // Layout with new size
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                getViewTreeObserver().removeOnGlobalLayoutListener(this);
                waiter.release();
            }
        });

        post(() -> {
            // Optionally, show loading view:
            if (isShowLoading) {
                addView(new LoadingView(getContext()));
            }
            // Request layout to release waiter:
            surfaceView.requestLayout();
        });

        waiter.acquire();

        // Run one render pass
        gpuVideo.runOnGLThread(waiter::release);
        requestRender();
        waiter.acquire();
        Bitmap bitmap = capture();


        forceSize = null;
        post(() -> surfaceView.requestLayout());
        requestRender();

        if (isShowLoading) {
            postDelayed(() -> {
                // Remove loading view
                removeViewAt(1);
            }, 300);
        }

        return bitmap;
    }

    /**
     * Capture the current image with the size as it is displayed and retrieve it as Bitmap.
     *
     * @return current output as Bitmap
     * @throws InterruptedException Semaphore异常
     */
    public Bitmap capture() throws InterruptedException {
        final Semaphore waiter = new Semaphore(0);

        final int width = surfaceView.getMeasuredWidth();
        final int height = surfaceView.getMeasuredHeight();

        // Take picture on OpenGL thread
        final Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        gpuVideo.runOnGLThread(() -> {
            GPUImageNativeLibrary.adjustBitmap(resultBitmap);
            waiter.release();
        });
        requestRender();
        waiter.acquire();

        return resultBitmap;
    }

    private static class LoadingView extends FrameLayout {
        public LoadingView(Context context) {
            super(context);
            init();
        }

        public LoadingView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }

        public LoadingView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init();
        }

        private void init() {
            ProgressBar view = new ProgressBar(getContext());
            view.setLayoutParams(
                    new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER));
            addView(view);
            setBackgroundColor(Color.BLACK);
        }
    }

    private class GPUVideoGLSurfaceView extends GLSurfaceView {
        public GPUVideoGLSurfaceView(Context context) {
            super(context);
        }

        public GPUVideoGLSurfaceView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (forceSize != null) {
                super.onMeasure(MeasureSpec.makeMeasureSpec(forceSize.getWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(forceSize.getHeight(), MeasureSpec.EXACTLY));
            } else {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

    private class GPUVideoGLTextureView extends GLTextureView {
        public GPUVideoGLTextureView(Context context) {
            super(context);
        }

        public GPUVideoGLTextureView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (forceSize != null) {
                super.onMeasure(MeasureSpec.makeMeasureSpec(forceSize.getWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(forceSize.getHeight(), MeasureSpec.EXACTLY));
            } else {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
}
